#include "ServerSocket.h"
#include "Listener.h"
#include <string>
#include <stdio.h>
#include <sstream>
#include "Log.h"
#include <iostream>

#define NO_INICIALIZADO 0
#define NO_CONECTADO 1
#define CONECTADO 2
#define ACEPTANDO_CONEXIONES 3
#define FIN_INESPERADO 4

ServerSocket::ServerSocket() {
    this->socket_puerto = PUERTO_NO_ABIERTO;
    this->listener = NULL;
    this->estado = NO_INICIALIZADO;
}

ServerSocket::ServerSocket(Listener *listener) {
    this->socket_puerto = PUERTO_NO_ABIERTO;
    this->listener = listener;
    this->estado = NO_INICIALIZADO;
}

ServerSocket::~ServerSocket() { }

/** Descripcion: devuelve true si se han seteado las estructuras internas
  *  del socket (si se ha ejecutado start() y definido el estado)
  */
bool ServerSocket::inicializado() {
    return estado != NO_INICIALIZADO;
}

bool ServerSocket::aceptando() {
    return estado == ACEPTANDO_CONEXIONES;
}

int ServerSocket::conectar() {
    int estado;
    int error = 1;
    int socketd;

    if (setAddrInfo() == 0) {
        if ((socketd = getSocketDescriptor()) != -1) {
            int yes = 1;
            estado = setsockopt(socketd, SOL_SOCKET, \
                                SO_REUSEADDR, &yes, sizeof(int));
            estado = bind(socketd, host_info_list->ai_addr, \
                          host_info_list->ai_addrlen);
            if (estado == -1) {
                Log::loguear(MDEBUG, "Error en el bind.");
            }  else {
                estado =  listen(socketd, MAX_COLA_ESPERA_SOCKET);
                if (estado == -1) {
                    Log::loguear(MDEBUG, "Error en el listen.");
                } else {
                    setSocketPuerto(socketd);
                    error = 0;
                    setEstado(CONECTADO);
                }
            }
        }
    }
    return error;
}

void ServerSocket::imprimir() {
    std::cout << this->aString() << std::endl;
}

void ServerSocket::cancelar() {
    if (getSocket() != PUERTO_NO_ABIERTO) {
        close(getSocket());
        setSocket(PUERTO_NO_ABIERTO);
    }
}

Socket *ServerSocket::getSocketAceptado() {
    return new Socket(this->getSocket());
}

void *ServerSocket::run() {
    int  new_sd = -1;
    struct sockaddr_storage their_addr;
    socklen_t addr_size;
    addr_size = sizeof(their_addr);
    bool shutdown = false;
    std::stringstream ss;
    this->socket_puerto = -1;

    if (this->conectar() == 0) {
        setEstado(ACEPTANDO_CONEXIONES);

        if (MODO_DEBUG == 1){
          ss << "El puerto " << getPuerto()
             << " esta aceptando conexiones por hilo " << getTID()
             << ", Socket puerto (accepts): " << this->getSocketPuerto();
          Log::loguear(MINFO, ss.str());
        }

        while (!shutdown) {
          new_sd = accept(this->socket_puerto, (struct sockaddr *)&their_addr,\
                          &addr_size);
          if (new_sd == -1) {
              Log::loguear(MDEBUG, "Socket apagado. No se aceptan más conexiones.");
              shutdown = true;
          } else {
              setSocket(new_sd);
              this->listener->atender(*this);
          }
        }
    } else {
        setEstado(FIN_INESPERADO);
        Log::loguear(MDEBUG, "Error conectando...");
    }
    this->exit();

    return NULL;
}

void ServerSocket::apagar() {
    if (MODO_DEBUG == 1) {
        std::string msg = "Apagando socket server: " + this->aString();
        Log::loguear(MDEBUG, (char *)msg.c_str());
    }
    shutdown(getSocketPuerto(), 2);
}

std::string ServerSocket::aString() {
    std::stringstream s;
    s << "Puerto: " << getPuerto() << ", id descriptor: " << getSocketPuerto();
    return s.str();
}
